home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1994 November / macformat-018.iso / Utility Spectacular / Text / Digest-Browser-1.6 Folder / Application / Browser.c next >
Encoding:
C/C++ Source or Header  |  1992-08-30  |  16.2 KB  |  646 lines  |  [TEXT/KAHL]

  1. /*
  2.  * Browser.c
  3.  *
  4.  * 9/5/91
  5.  * Manuel A. Perez
  6.  *
  7.  * 9/5/91 Added a simple mac front end to the skim-digest program
  8.  * from sumex.
  9.  *
  10.  * 9/6/91 Liked it so much, that I decided this was worth making
  11.  * a TCL application out of it.
  12.  *
  13.  * 2/29/92 Reorganized the parser.  More error checks.  It now
  14.  * detects Date, From, and Subject.  All three of them are kept
  15.  * in the directory structure.  Also, this file can be tested
  16.  * without the full application.
  17.  *
  18.  * 3/9/92 Fixed problem with BuildHeadMessage.
  19.  * Based on: skim-digest.c (stored in info-mac)
  20.  * version 1.0, 28 Feb 91, by Mike Gleason, NCEMRSoft.
  21.  *
  22.  * 8/29/92 Added the functionality to mark items
  23.  */
  24.  
  25.  
  26. #include <stdio.h>
  27. #include <string.h>                // str-stuff
  28. #include "Browser.h"
  29. #include <Exceptions.h>
  30.  
  31. // Constants
  32. #define KEY "From:"
  33. #define KEYLEN 5
  34.  
  35. // Prototypes
  36. int select_index_line(void);
  37. int line_type(char *line);
  38. void copystr(register char *to, register char *from);
  39. Boolean find_first_header(FILE *fp);
  40. Boolean find_next_header(FILE *fp, fpos_t *last_blank_line);
  41. void get_article_info(BrowserItemPtr p);
  42. Boolean BuildHeadMessage(BrowserDir *dir);
  43.  
  44. // Uncomment the next line to test this program without TCL.
  45. //#define MAIN
  46.  
  47. #ifdef MAIN
  48. #define REPORT_ERROR(MSG)        fprintf(stderr, MSG)
  49. #define REPORT_F_NOT_OPENED(FNAME)         fprintf(stderr, "skim-digest: \"%s\" could not be opened.\n", FNAME)
  50. #define REPORT_EOF() fprintf(stderr, "skim-digest: Unexpected EOF\n")
  51. #define REPORT_MEMORY_ALOC() fprintf(stderr, "skim-digest: error allocating memory\n")
  52. #define REPORT_IMPROPER_FMT()    fprintf(stderr, "skim-digest: improper file format\n")
  53. #else
  54.  
  55. #define REPORT_ERROR(MSG)            FailMemError()
  56. #define REPORT_F_NOT_OPENED(FNAME)    Failure(1000, SpecifyMsg(1025, 1))
  57. #define REPORT_EOF()                 Failure(1001, SpecifyMsg(1025, 2))
  58. #define REPORT_MEMORY_ALOC()         FailMemError()
  59. #define REPORT_IMPROPER_FMT()        Failure(1000, SpecifyMsg(1025, 3))
  60.  
  61. #endif
  62.  
  63. #ifdef MAIN
  64. main()
  65. {
  66. BrowserDir    dir;
  67. SFTypeList typeList;
  68. SFReply theReply;
  69. Point at = {40, 40};
  70. BrowserItemPtr list, p, q;
  71.  
  72.  
  73.     typeList[0] = 'TEXT';
  74.     SFGetFile(at, "\pGet File", NULL, 1, typeList, NULL, &theReply);
  75.     while (theReply.good) {
  76.         brInitDir(&dir);
  77.         PtoCstr(theReply.fName);
  78.         strcpy(dir.fname, (char *)&theReply.fName);
  79.         CtoPstr(theReply.fName);
  80.         dir.vRefNum = theReply.vRefNum;
  81.     
  82.         if (BuildBrowserIndex(&dir)) {
  83.  
  84.             p = dir.topItem;
  85.             while (p) {
  86.                 printf("Date: [%s]\n", p->date);
  87.                 printf("From: [%s]\n", p->from);
  88.                 printf("Subject: [%s]\n", p->subject);
  89.                 printf("\n\n");
  90.                 p = p->next;
  91.             }
  92.             //fclose(dir->fp);
  93.         }
  94.         SFGetFile(at, "\pGet File", NULL, 1, typeList, NULL, &theReply);
  95.     }
  96. }
  97. #endif
  98.  
  99. /*----------------------------------------------------------------------------*/
  100. /*----------------------------------------------------------------------------*/
  101. /*----------------------------------------------------------------------------*/
  102. void    brInitDir(BrowserDirPtr dir)
  103. {
  104.     if (dir) {
  105.         dir->fname[0] = '\0';
  106.         dir->fp = NULL;
  107.         dir->numArticles = 0;
  108.         dir->markArticles = 0;
  109.         dir->topItem = NULL;
  110.     }
  111. }
  112.  
  113. /*----------------------------------------------------------------------------*/
  114. long    brItemCount(BrowserDirPtr dir)
  115. {
  116.     if (dir)
  117.         return dir->numArticles;
  118.     else
  119.         return 0;
  120. }
  121.  
  122. /*----------------------------------------------------------------------------*/
  123. long    brMarkCount(BrowserDirPtr dir)
  124. {
  125.     if (dir)
  126.         return dir->markArticles;
  127.     else
  128.         return 0;
  129. }
  130.  
  131. /*----------------------------------------------------------------------------*/
  132. void    brInitItem(BrowserItemPtr item)
  133. {
  134.     if (item) {
  135.         item->next = NULL;
  136.         item->owner = NULL;
  137.         item->fp = NULL;
  138.         item->date[0] = '\0';
  139.         item->from[0] = '\0';
  140.         item->subject[0] = '\0';
  141.         item->composite[0] = '\0';
  142.         item->startAt = -1;
  143.         item->endAt = -1;
  144.         item->marked = false;
  145.     }
  146. }
  147.  
  148. /*----------------------------------------------------------------------------*/
  149. void    brSetOwner(BrowserItemPtr item, BrowserDirPtr dir)
  150. {
  151.     if (item)
  152.         item->owner = dir;
  153. }
  154.  
  155. /*----------------------------------------------------------------------------*/
  156. BrowserDirPtr    brGetOwner(BrowserItemPtr item)
  157. {
  158.     if (item)
  159.         return (BrowserDirPtr)item->owner;
  160.     else
  161.         return NULL;
  162. }
  163.  
  164. /*----------------------------------------------------------------------------*/
  165. void    brSetFP(BrowserItemPtr item, FILE *file)
  166. {
  167. BrowserDirPtr owner;
  168.  
  169.     if (item) {
  170.         item->fp = file;
  171.         
  172.         // update owner
  173.         if (owner = brGetOwner(item))
  174.             owner->fp = file;
  175.     }
  176. }
  177.  
  178. /*----------------------------------------------------------------------------*/
  179. FILE    *brGetFP(BrowserItemPtr item)
  180. {
  181.     if (item)
  182.         return item->fp;
  183.     else
  184.         return NULL;
  185. }
  186.  
  187. /*----------------------------------------------------------------------------*/
  188. void    brToggleMark(BrowserItemPtr item)
  189. {
  190. Boolean old;
  191. BrowserDirPtr owner;
  192.  
  193.     if (item) {
  194.         old = item->marked;
  195.         item->marked = !(item->marked);
  196.  
  197.         // update count on directory
  198.         owner = brGetOwner(item);
  199.         if (old) {
  200.             // decrement count
  201.             owner->markArticles--;
  202.         }
  203.         else {
  204.             // increment count
  205.             owner->markArticles++;
  206.         }
  207.     }
  208. }
  209.  
  210. /*----------------------------------------------------------------------------*/
  211. void    brSetMark(BrowserItemPtr item, Boolean mark)
  212. {
  213. Boolean old;
  214. BrowserDirPtr owner;
  215.  
  216.     if (item) {
  217.         old = item->marked;
  218.         item->marked = mark;
  219.  
  220.         // update count on directory
  221.         if (old && !mark) {
  222.             // decrement count
  223.             owner->markArticles--;
  224.         }
  225.         else if (!old && mark) {
  226.             // increment count
  227.             owner->markArticles++;
  228.         }
  229.     }
  230. }
  231.  
  232. /*----------------------------------------------------------------------------*/
  233. Boolean    brGetMark(BrowserItemPtr item)
  234. {
  235.     if (item)
  236.         return item->marked;
  237.     else
  238.         return false;
  239. }
  240.  
  241. /*----------------------------------------------------------------------------*/
  242. void    brSetStart(BrowserItemPtr item, long start)
  243. {
  244.     if (item)
  245.         item->startAt = start;
  246. }
  247.  
  248. /*----------------------------------------------------------------------------*/
  249. long    brGetStart(BrowserItemPtr item)
  250. {
  251.     if (item)
  252.         return item->startAt;
  253.     else
  254.         return -1;
  255. }
  256.  
  257. /*----------------------------------------------------------------------------*/
  258. void    brSetEnd(BrowserItemPtr item, long end)
  259. {
  260.     if (item)
  261.         item->endAt = end;
  262. }
  263.  
  264. /*----------------------------------------------------------------------------*/
  265. long    brGetEnd(BrowserItemPtr item)
  266. {
  267.     if (item)
  268.         return item->endAt;
  269.     else
  270.         return -1;
  271. }
  272.  
  273. /*----------------------------------------------------------------------------*/
  274. void    brSetNext(BrowserItemPtr item, BrowserItemPtr nxt)
  275. {
  276.     if (item)
  277.         item->next = nxt;
  278. }
  279.  
  280. /*----------------------------------------------------------------------------*/
  281. BrowserItemPtr    brGetNext(BrowserItemPtr item)
  282. {
  283.     if (item)
  284.         return item->next;
  285. }
  286.  
  287. /*----------------------------------------------------------------------------*/
  288. Boolean equalstr(register char *s, register char *t, int n)
  289. {
  290. register int i;
  291.  
  292.     for (i = 0; i < n; i++, s++, t++)
  293.         if (*s != *t)
  294.             return false;
  295.         else if (*s == '\0' || *t == '\0')
  296.             return false;
  297.  
  298.     return true;
  299. }   /* equalstr */
  300.  
  301. /*----------------------------------------------------------------------------*/
  302. int select_index_line(void)
  303. {
  304.     return line_type("From:");
  305. }
  306.  
  307. /*----------------------------------------------------------------------------*/
  308. int line_type(char *line)
  309. {
  310. register int i = 1;
  311. Str255 string;
  312.  
  313.     do {
  314.         GetIndString(string, 132, i);
  315.         if (string[0] && equalstr((char *)&string[1], line, string[0]))
  316.             return i;
  317.     } while (string[0] != 0);
  318.     return 0;
  319. }
  320.  
  321. /*----------------------------------------------------------------------------*/
  322. void copystr(register char *to, register char *from)
  323. {
  324.  
  325.     if (!(to && from)) return;
  326.  
  327.     // advance string pointer until ':'
  328.     for (; *from && *from != ':'; from++)
  329.         ;/* nothing */
  330.  
  331.     // skip over white spaces
  332.     if (*from)
  333.         for (from++; *from && *from == ' '; from++)
  334.             ;/* nothing */
  335.  
  336.     strcpy(to, from);
  337.     if (*from)
  338.         to[strlen(to) - 1] = '\0';        // remove nl at end of line
  339. }
  340.  
  341. /*----------------------------------------------------------------------------*/
  342. /*
  343.  * The format of the file has:
  344.  *
  345.  * A message from the Info-mac administrator at the top,
  346.  * Followed by messages with the following format:
  347.  *
  348.  *    <blank line>
  349.  *    Date:
  350.  *    From:
  351.  *    Subject:        << this line seems optional
  352.  *    <blank line>
  353.  *    <message>        << will include blank lines
  354.  *    <blank line>
  355.  *    ------            << dash line separates the messages
  356.  *
  357.  * But, we can only be garanteed that the 'From' appears at the
  358.  * beginning of the line only when it is part of the header.
  359.  *
  360.  * So to parse the header we will do the following:
  361.  *  1) Search for a line beginning with From, while
  362.  *       remembering the last blank line seen.
  363.  *    2) Once a From line is found, look ahead for the
  364.  *       next blank line.
  365.  *    3) The text between the first blank line, and the
  366.  *       second blank line contains the header for the
  367.  *       mail message.  Search this chunck of text to
  368.  *       get the Date, From, and Subject.
  369.  *
  370.  */
  371.  
  372. // Look from the beginning of the file and determine if the
  373. // first few lines contains what looks like a header for this
  374. // type of file. (Looking at the previous comments, the first
  375. // header should not have the leading blank lines).
  376.  
  377. Boolean find_first_header(FILE *fp)
  378. {
  379. char buffer[200];
  380.  
  381.     fseek(fp, 0, SEEK_SET);        // set file position
  382.     while (fgets(buffer, sizeof(buffer), fp)) {
  383.  
  384.         if (equalstr(buffer, "\n", 1))    // found an empty line
  385.             //return false;
  386.             continue;
  387.  
  388.         else if (equalstr(buffer, KEY, KEYLEN))
  389.             // found a From:
  390.             return true;
  391.  
  392. // JRB addition - allow first index to start with anything
  393.         else // if (equalstr(buffer, "C.S.M.P. Digest", 15))
  394.             return true;
  395. // end JRB addition
  396.     }
  397.  
  398.     return false;
  399. }
  400.  
  401. // Look ahead on the file for the beginning of the next
  402. // message file.  Return the file position of where the
  403. // next message begins.
  404. Boolean find_next_header(FILE *fp, fpos_t *last_blank_line)
  405. {
  406. fpos_t next_message;
  407. char buffer[200];
  408. Boolean found_blank_line;
  409. Boolean empty_line;
  410. Boolean found_header;
  411.  
  412.     fgetpos(fp, &next_message);        // save file position
  413.     *last_blank_line = next_message;
  414.  
  415.     found_blank_line = false;
  416.     found_header = false;
  417.     while (fgets(buffer, sizeof(buffer), fp)) {
  418.  
  419.         empty_line = equalstr(buffer, "\n", 1);
  420.         if (!found_blank_line && empty_line) {    // found an empty line
  421.             *last_blank_line = next_message;
  422.             found_blank_line = true;
  423.         }
  424.  
  425.         else if (found_blank_line && equalstr(buffer, KEY, KEYLEN)) {
  426.             // found a From: after a blank line and before another
  427.             // blank line
  428.             found_header = true;
  429.             break;            // get out
  430.         }
  431.  
  432.         else if (found_blank_line && empty_line) {
  433.             *last_blank_line = next_message;
  434.             found_blank_line = true;
  435.         }
  436.  
  437.         fgetpos(fp, &next_message);
  438.     }
  439.  
  440.     return found_header;
  441. }
  442.  
  443. /*----------------------------------------------------------------------------*/
  444. void get_article_info(BrowserItemPtr p)
  445. {
  446. char  buffer[128];
  447. fpos_t start;
  448. // JRB addition
  449. char *compositePtr;
  450. char separator[10] = "; ";
  451. char firstLine[MAX_STRING];
  452. short subLength,dateLength,fromLength,sepLength;
  453. // end JRB addition
  454.  
  455.     fgetpos(brGetFP(p), &start);
  456.  
  457.     p->from[0] = p->date[0] = p->subject[0] = '\0';
  458. // JRB addition - initialize string for new index option
  459.     p->composite[0] = '\0';
  460.  
  461.     // skip over blank lines
  462.     do {
  463.         fgets(buffer, sizeof(buffer), brGetFP(p));
  464.     } while(equalstr(buffer, "\n", 1));
  465.  
  466. // JRB addition - save first line in case we want to use it for the index if we find nothing better
  467.     strcpy(firstLine,buffer);    
  468.     firstLine[strlen(firstLine) - 1] = '\0';        
  469. // end JRB addition
  470.  
  471.  
  472.     // process lines until another blank line comes along
  473.     while (!equalstr(buffer, "\n", 1))  {
  474.         if (equalstr(buffer, "From:", 5))
  475.             copystr(p->from, buffer);                // Copy From:
  476.         else if (equalstr(buffer, "Date:", 5))
  477.             copystr(p->date, buffer);                // Copy Date:
  478.         else if (equalstr(buffer, "Subject:", 8))
  479.             copystr(p->subject, buffer);            // Copy Subject:
  480.         fgets(buffer, sizeof(buffer), brGetFP(p));
  481.     }
  482.  
  483. // JRB addition - allow first index to be something other than "From:"
  484.     if ((p->from[0] == '\0') && (p->date[0] == '\0') && (p->subject[0] == '\0')) 
  485.         strcpy(p->subject,firstLine);
  486. // end JRB addition
  487.  
  488.     if (p->from[0] == '\0')        strcpy(p->from, "-");
  489.     if (p->date[0] == '\0')        strcpy(p->date, "-");
  490.     if (p->subject[0] == '\0')     strcpy(p->subject, "-");
  491.  
  492. // JRB addition - new index option
  493.     sepLength = strlen(separator);
  494.     if ((subLength=strlen(p->subject))+sepLength < MAX_STRING) {
  495.         strcpy(compositePtr=p->composite, p->subject);
  496.         if (subLength+(fromLength=strlen(p->from))+sepLength < MAX_STRING) {
  497.             if (fromLength>1) {
  498.                 strcpy(compositePtr+=subLength, separator);
  499.                 strcpy(compositePtr+=sepLength, p->from);
  500.             }
  501.             if (subLength+fromLength+(dateLength=strlen(p->date))+2*sepLength < MAX_STRING) {
  502.                 if (dateLength>1) {
  503.                     strcpy(compositePtr+=fromLength, separator);
  504.                     strcpy(compositePtr+=sepLength, p->date);
  505.                 }
  506.             }
  507.         }
  508.     }
  509. // end JRB addition
  510. }
  511.  
  512. /*----------------------------------------------------------------------------*/
  513. Boolean BuildBrowserIndex(BrowserDir *dir)
  514. {
  515. char  buffer[128];
  516. short articleNum = 0;
  517. BrowserItemPtr p, q;
  518. fpos_t this_article;
  519. fpos_t next_article;
  520. short saveVol;
  521.  
  522.     GetVol(NULL, &saveVol);
  523.     SetVol(NULL, dir->vRefNum);
  524.     if (!(dir->fp = fopen(dir->fname, "r"))) {
  525.         SetVol(NULL, saveVol);
  526.         REPORT_F_NOT_OPENED(dir->fname);
  527.         return false;
  528.     }
  529.     SetVol(NULL, saveVol);
  530.  
  531.     dir->topItem = NULL;    // initialize head pointer
  532.  
  533.     if (!BuildHeadMessage(dir))    // if error, return
  534.         return false;
  535.  
  536.     q = p = dir->topItem;    // initialize temp pointers (q, p)
  537.     articleNum = 1;            // we already have one article
  538.  
  539.     // Find the beginning of the next article as an indicator
  540.     // of the end of this one. 'find_next_header' returns TRUE
  541.     // if it finds the beginning of the next header
  542.     this_article = dir->topItem->endAt;
  543.     fsetpos(dir->fp, &this_article);
  544.     while (find_next_header(dir->fp, &next_article)) {
  545.  
  546.         // found new one; store new information
  547.         if (q = Allocate(BrowserItem)) {
  548.  
  549.             brInitItem(q);
  550.             brSetOwner(q, dir);
  551.             // keep file pointer at every node, duplication of effort
  552.             brSetFP(q, dir->fp);    //q->fp = dir->fp;    // but worth it (see CDisplayText)
  553.  
  554.             // Set header to the beginning of this article, then
  555.             // get the header information, and find the end of it
  556.             fsetpos(dir->fp, &this_article);            // start
  557.             get_article_info(q);    // store Date, From and Subject
  558.             find_next_header(dir->fp, &next_article);
  559.  
  560.             brSetStart(q, this_article);    //q->startAt = this_article;
  561.             brSetEnd(q, next_article);        //q->endAt = next_article;
  562.             brSetMark(q, false);            //q->marked = false;
  563.             brSetNext(q, NULL);                //q->next = NULL;
  564.  
  565.             // set info for previous node
  566.             if (p) {
  567.                 //p->endAt = q->startAt;    // ends at beginning of current one
  568.                 brSetEnd(p, brGetStart(q));
  569.                 //p->next = q;            // set sequential link
  570.                 brSetNext(p, q);
  571.             }
  572.  
  573.             // copy pointer over
  574.             p = q;
  575.  
  576.             // increment article count
  577.             articleNum++;
  578.             this_article = next_article;
  579.             fsetpos(dir->fp, &next_article);
  580.         }
  581.  
  582.         else {
  583.             REPORT_ERROR("skim-digest: error allocating memory\n");
  584.             return false;
  585.         }
  586.     }
  587.  
  588.     // when done, set the end of the link
  589.     if (p) {
  590.         brSetEnd(p, next_article);        //p->endAt = next_article;
  591.         brSetNext(p, NULL);                //p->next = NULL;
  592.     }
  593.     dir->numArticles = articleNum;
  594.  
  595.     return true;
  596. }  /* BuildBrowserIndex */
  597.  
  598. /*----------------------------------------------------------------------------*/
  599. Boolean BuildHeadMessage(BrowserDir *dir)
  600. {
  601. char  buffer[128];
  602. fpos_t start;
  603. fpos_t end;
  604. Boolean found_new_line = false;
  605.  
  606.     // Go through the digest until a line KEY follows a blank line
  607.     fseek(dir->fp, 0, SEEK_SET);    // set file position
  608.     fgetpos(dir->fp, &start);        // save file position
  609.     if (!find_first_header(dir->fp)) {
  610.         REPORT_IMPROPER_FMT();
  611.         return false;
  612.     }
  613.     else if (dir->topItem = Allocate(BrowserItem)) {
  614.         brInitItem(dir->topItem);
  615.         brSetOwner(dir->topItem, dir);
  616.         brSetFP(dir->topItem, dir->fp);    // keep file pointer at every node,
  617.             // duplication of effort, but worth it (see CDisplayText)
  618.  
  619.         fsetpos(dir->fp, &start);    // set file position
  620.         find_next_header(dir->fp, &end);    // get end of article
  621.  
  622.         fsetpos(dir->fp, &start);    // set file position
  623.         get_article_info(dir->topItem);        // get article info
  624.  
  625.         // this is a hack to allow blank lines at the top of the
  626.         // file.
  627.         if (start == end)
  628.             find_next_header(dir->fp, &end);    // get real end of article
  629.     
  630.         brSetStart(dir->topItem, start);        //dir->topItem->startAt = start;
  631.         brSetEnd(dir->topItem, end);            //dir->topItem->endAt = end;
  632.         brSetMark(dir->topItem, false);            //dir->topItem->marked = false;
  633.         brSetNext(dir->topItem, NULL);            //dir->topItem->next = NULL;
  634.         
  635.         // leave pointer at the end of the header
  636.         fsetpos(dir->fp, &end);
  637.         return true;
  638.     }
  639.     else {
  640.         REPORT_MEMORY_ALOC();
  641.         return false;
  642.     }
  643.  
  644. }  /* BuildHeadMessage */
  645.  
  646.